#include <string.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <user/sys_fio.h>


#include "parse.h"
#include "commands.h"
#include "shell_defs.h"
#include "shell_vars.h"
#include "script.h"



struct token_info{
    char *token;
    int  code;
};

struct token_info tokens[]={
    {"echo",SVO_ECHO},
    {"exec",SVO_EXEC},
    {"exit",SVO_EXIT},
    {"if",SVO_IF},
    {"goto",SVO_GOTO},
    {"number",SVO_NUMBER_CAST},
    {"string",SVO_STRING_CAST},
    {"+",SVO_ADD},
    {"-",SVO_SUB},
    {"*",SVO_MUL},
    {"/",SVO_DIV},
    {"=",SVO_LOAD},
    {"==",SVO_EQUAL},
    {"!=",SVO_NOT_EQUAL},
    {">",SVO_BIGGER},
    {">=",SVO_BIGGER_E},
    {"<",SVO_LESS},
    {"<=",SVO_LESS_E},
    {"$",SVO_DOLLAR},
    {",",SVO_COMMA},
    {"(",SVO_PARENL},
    {")",SVO_PARENR},
    {":",SVO_DOTDOT},
    {NULL,SVO_NONE}
};


static int token_to_id(const char *token)
{
    int cnt = 0;

    while((tokens[cnt].token != NULL) && (strcmp(tokens[cnt].token,token) != 0))  cnt++;

    return tokens[cnt].code;
}

char *id_to_token(int id)
{
    int cnt = 0;

    switch(id){

    case SVO_IDENT			:return "ident";
    case SVO_SQ_STRING	:
    case SVO_DQ_STRING	:return "string";
    case SVO_NUMBER		:return "number";
    case SVO_END         :return "end";
    }

    while((tokens[cnt].token != NULL) && (tokens[cnt].code != id))  cnt++;

    if(tokens[cnt].token != NULL){

        return tokens[cnt].token;

    } else {

        return "unkown";

    }
}

static int  scan_string(scan_info *info,int max_len)
{
    const char **in=&(info->scanner);
    char ch=**in;
    char *scan=info->token;
    int  len_cnt = max_len;
    int err = 0;

    (*in)++;

    while((**in != 0) && (**in != ch)){

        if(len_cnt > 0){

            *scan = **in;
            scan++;
            len_cnt--;

        }

        (*in)++;

    }

    if(**in != 0) {
        (*in)++;
    } else {
        info->scan_error = SHE_MISSING_QOUTE;
        err = 1;
    }

    *scan = 0;
    return err;
}



int parse_line(const char *buf, char *argv[], int max_args, char *redirect_in, char *redirect_out)
{
    const char *scan=buf;
    const char *type_char;
    const char *start;
    char out[SCAN_SIZE+1];
    char tmp[SCAN_SIZE+1];
    char *arg_tmp;
    int arg_cnt = 0;
    int len = 0;
    int replace;
    //int param;
    redirect_in[0] = 0;
    redirect_out[0] = 0;

//printf("parse '%s'", buf );

    while(1){

        while((*scan != 0) && (isspace(*scan))) scan++;

        if(*scan == 0) break;

        replace = 1;
        type_char = scan;
        start = scan;
        //param = 1;

        switch(*type_char){
        case '\'':
            replace  = 0;
            /* fall through to */
            /* FALLTHROUGH */
        case  '"':
            scan++;
            start++;
            while((*scan != 0) && (*scan != *start)) scan++;
            break;

        case '>':
            /* fall through to */
            /* FALLTHROUGH */
        case '<':
            start++;
            while((*start != 0) && (isspace(*start))) start++;
            scan = start;

        default:

            while((*scan != 0) && (!isspace(*scan))) scan++;

        }

        len = scan - start;
        if(*scan != 0) scan++;

        if(replace){

            memcpy(tmp,start,len);
            tmp[len]=0;
            parse_vars_in_string(tmp,out,SCAN_SIZE);

        } else {

            memcpy(out,start,len);
            out[len]=0;

        }

        switch(*type_char){

        case '>':
            strcpy(redirect_out,out);
            break;

        case '<':
            strcpy(redirect_in,out);
            break;

        default:
            if(arg_cnt < max_args){

                arg_tmp = shell_strdup(out);
                if(arg_tmp != NULL) argv[arg_cnt++] = arg_tmp;

            }

        }
    }

    return arg_cnt;
}



static void scan_ident(const char **in,char *out,int max_len)
{
    char *scan = out;
    int len_cnt = max_len;

    while ((**in != 0) && (IS_IDENT_CHAR(**in))){
        if(len_cnt > 0){
            *scan = **in;
            scan++;
            len_cnt--;
        }
        (*in)++;
    }
    *scan = 0;
}

static void scan_number(const char **in,char *out,int max_len)
{
    char *scan = out;
    int len_cnt = max_len;

    while ( (**in != 0) && (isdigit(**in)) ){

        if(len_cnt > 0){

            *scan = **in;
            scan++;
            len_cnt--;

        }

        (*in)++;

    }
    *scan = 0;
}


static void scan_comp(const char **in,char *out,int max_len)
{
    int num_cnt = max_len;
    char *out_scan = out;

    *out_scan  = **in;
    out_scan++;
    (*in)++;
    num_cnt--;

    if(num_cnt > 0) {

        if(**in == '='){

            *out_scan = **in;
            out_scan++;
            (*in)++;
            num_cnt--;

        }
    }
    *out_scan = 0;
}

int expect(scan_info *info,int check)
{
    if(info->sym_code == check){

        (void)scan(info);
        return 0;

    } else {

        return 1;

    }
}


int scan_info_next_line(scan_info *info)
{
    if(info->current == NULL) return 0;

    info->current = info->current->next;

    if(info->current == NULL) return 0;

    info->line_no++;

    return set_scan_info_line(info);
}

int scan_info_home(scan_info *info)
{
    info->current = info->data.list;
    info->line_no = 1;
    return set_scan_info_line(info);
}


int init_scan_info_by_file(const char *file_name,scan_info *info)
{
    int err = SHE_NO_ERROR;

    err = read_text_file(file_name,&(info->data));

    if(err != SHE_NO_ERROR) return err;

    info->scanner    = NULL;
    info->scan_error = SHE_NO_ERROR;

    if(scan_info_home(info)) err  = SHE_SCAN_ERROR;

    return err;

}

int set_scan_info_line(scan_info *info)
{

    if(info->current == NULL) return 1;

    strlcpy(info->input_line,info->current->text, sizeof(info->input_line));
    info->scanner = (info->input_line);
    return scan(info);

}

// fetch next token
//      **in  scan string
//      *out  token
//      return token type (STY_<> vars)


void init_scan_info(const char*in,scan_info *info){

    strncpy(info->input_line,in,SCAN_SIZE);

    info->input_line[SCAN_SIZE] = 0;
    info->scanner=(const char*)info->input_line;
    info->scan_error = SHE_NO_ERROR;
    info->current = NULL;
    info->line_no = 1;

    (void)scan(info);

}

int scan(scan_info *info)
{
    int err = 0;
    int sym_code = SVO_NONE;
    const char **in = &(info->scanner);
    char tmp[SCAN_SIZE+1];

    while((**in != 0) && (isspace(**in))) (*in)++;

    if(isalpha(**in)){

        scan_ident(in,info->token,SCAN_SIZE);

        sym_code = token_to_id(info->token);
        if(sym_code == SVO_NONE) sym_code = SVO_IDENT;

    } else if(isdigit(**in)){

        scan_number(in,info->token,SCAN_SIZE);
        sym_code = SVO_NUMBER;
    } else{
        switch(**in){
        case '#':
            sym_code = SVO_END;
            break;
        case '>':
        case '<':
        case '=':
        case '!':

            scan_comp(in,info->token,SCAN_SIZE);
            break;

        case '"':

            err = scan_string(info,SCAN_SIZE);

            if(err != SHE_NO_ERROR){
                parse_vars_in_string(info->token,tmp,SCAN_SIZE);
                strncpy(info->token,tmp,SCAN_SIZE);
                info->token[SCAN_SIZE] = 0;
            }

            sym_code = SVO_DQ_STRING;
            break;
        case '\'':

            sym_code = SVO_SQ_STRING;
            err =scan_string(info,SCAN_SIZE);
            break;
        case 0:

            *(info->token) = 0;
            sym_code = SVO_END;
            break;
        default:
            info->token[0] = **in;
            info->token[1] = 0;
            (*in)++;
            break;

        }
        if(sym_code == SVO_NONE) sym_code = token_to_id(info->token);

    }

    info->sym_code = sym_code;

    return err;
}

void parse_vars_in_string(const char *string,char *out,int max_len)
{
    const char *scan= string;
    char buf[SCAN_SIZE + 1];
    char *dest;
    char *out_scan = out;
    shell_value *value;
    int out_len = 0;
    int len_cnt;
    int out_max_len= max_len;
    int can_move;
    char *text;

    while((*scan != 0) && (*scan != '\'') && (out_max_len > 0)){
        if(*scan == '$'){
            scan++;
            dest = buf;
            len_cnt = SCAN_SIZE;

            while ((*scan != 0) && (IS_IDENT_CHAR(*scan)) &&(len_cnt > 0)){

                *dest = *scan;
                dest++;
                scan++;
                len_cnt--;

            }

            *dest = 0;
            value = get_value_by_name(buf);

            if(value != NULL){

                text = shell_value_to_char(value);
                can_move = strlen(text);

                if(can_move > out_max_len) can_move = out_max_len;

                memcpy(out_scan,text,can_move);
                out_max_len -= can_move;
                out_scan += can_move;

                free(text);

            }

        } else{

            *out_scan = *scan;
            out_len++;
            out_scan++;
            scan ++;
            out_max_len--;

        }
    }
    *out_scan= 0;
}

#define REDIR 0


static int launch(int (*cmd)(int, char **), int argc, char **argv, char *r_in, char *r_out)
{
    (void) r_in; // r_out will bother us to implement redir :)

    int retval= 0;
#if REDIR
    int saved_in;
    int saved_out;
    int new_in;
    int new_out;
    int err;

    if(strcmp(r_in, "")!= 0) {
        new_in = open(r_in, 0);
        if(new_in < 0) {
            //new_in = _kern_create(r_in);
            new_in = creat(r_in, 0666);
        }
    } else {
        new_in = dup(0);
    }
    if(new_in < 0) {
        err = new_in;
        goto err_1;
    }

    if(strcmp(r_out, "")!= 0) {
        new_out = open(r_out, 0);
        if(new_out < 0){
            //new_out = _kern_create(r_out);
            new_out = creat(r_out, 0666);
        }
    } else {
        new_out = dup(1);
    }
    if(new_out < 0) {
        err = new_out;
        goto err_2;
    }


    saved_in = dup(0);
    saved_out= dup(1);

    dup2(new_in, 0);
    dup2(new_out, 1);
    close(new_in);
    close(new_out);
#endif
    retval= cmd(argc, argv);
    if( retval )
        printf("exit code %d", retval);
#if REDIR
    dup2(saved_in, 0);
    dup2(saved_out, 1);
    close(saved_in);
    close(saved_out);
#endif
    return 0;

#if REDIR
err_2:
    close(new_in);
err_1:
    return err;
#endif
}


int shell_parse(const char *buf, int len)
{
    (void) len;

    int i;
    int  argc;
    char *argv[64];
    char redirect_in[256];
    char redirect_out[256];
    cmd_handler_proc *handler = NULL;
    int cnt;
    int err;

    // search for the command
    argc = parse_line(buf, argv, 64, redirect_in, redirect_out);

    if(argc == 0) return 0;

    handler = (void *)&cmd_create_proc;

    for(i=0; cmds[i].cmd_handler != NULL; i++) {
        if(strcmp(cmds[i].cmd_text,argv[0]) == 0){
            handler = cmds[i].cmd_handler;
            break;
        }
    }

    err = launch(handler, argc, argv, redirect_in, redirect_out);

    for(cnt = 0;cnt <argc;cnt++) free(argv[cnt]);

    return err;
}





